package com.sky.blue.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;


/**
 * @author lzh
 * @date 2019/11/10 - 10:10
 * 参考 https://blog.csdn.net/wangmx1993328/article/details/82150290
 * https://blog.csdn.net/huanggang028/article/details/41446707
 */
@Component
public class FtpUtils {

    private static final Logger log= LoggerFactory.getLogger(FtpUtils.class);




    // A）主动被动模式选择：FTP主动模式和被动模式的详细介绍可以参考（http://blog.csdn.net/huanggang028/article/details/41248663）。大概意思就是主动模式是客户端向服务端发送PORT命令，然后服务端通过20数据端口连接客户端开启的端口，然后发送数据；被动模式是客户端向服务端发送PASV命令，服务端随机开启一个端口并通知客户端，客户端根据该端口与服务端建立连接，然后发送数据。服务端是两种模式的，使用哪种模式取决于客户端，同时关键点在于网络环境适合用哪种模式，比如客户端在防火墙内，则最好选择被动模式。ftpClient.enterLocalActiveMode()便是配置成主动模式，而ftpClient.enterLocalPassiveMode()则配置成被动模式。
    //
    //        B）文件格式选择：默认是类型是ASCII（普通文本文件），但是为了使得支持任何类型，建议设置成BINARY_FILE_TYPE，并且是在上传和下载操作开始前调用setFileType(int fileType)。
    //
    //       C）上传、下载重载方法的使用选择：比如上传，boolean storeFile(String remote, InputStream local)和OutputStream storeFileStream(String remote)，差别是第一个方法如果我们不用关心到底已经从InputStream中传递了多少字节，完全依靠接口内部实现完成输入输出操作，而第二个方法使得我们可以在上传过程中控制传送字节速度等额外操作，比如传送进度的实现便可以依赖此方法。但是，需要注意的是，如果采用了第二个方法来做， 必须靠我们去实现是否上传完毕、下载完毕的事后控制，否则会出现问题。比如我们采用第二个方法，需要手动关闭流，同时，紧接着调用completePendingCommand()方法。如果使用第一个方法不能调用completePendingCommand()方法，因为它一旦被调用， 会一直等待FTP服务器返回226Transfer complete，但是FTP服务器只有在接受流执行close方法时，才会返回。不该调用的时候调用了便会出现僵死现象，同时报错
    //————————————————
    //版权声明：本文为CSDN博主「huanggang028」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。
    //原文链接：https://blog.csdn.net/huanggang028/article/details/41446707





    /**
     * 连接、登陆FTP服务器
     * @param ftpHost
     * @param ftpUserName
     * @param ftpPassword
     * @param ftpPort
     * @return
     */
    public static FTPClient openFtpClient(String ftpHost, String ftpUserName,
                                         String ftpPassword, int ftpPort) {
        FTPClient ftpClient = new FTPClient();
        try {
            ftpClient = new FTPClient();
            // 连接FTP服务器
            ftpClient.connect(ftpHost, ftpPort);
            // 登陆FTP服务器
            ftpClient.login(ftpUserName, ftpPassword);
            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                log.info("未连接到FTP，用户名或密码错误。");
                ftpClient.disconnect();
            } else {
                log.info("FTP登录成功。");
            }
        } catch (SocketException e) {
            log.info("FTP的IP地址可能错误，请正确配置。");
            e.printStackTrace();
        } catch (IOException e) {
            log.info("FTP的端口错误,请正确配置。");
            e.printStackTrace();
        }
        return ftpClient;
    }


    /**
     * ftp上传文件
     * @param readyClient  已登录的FTPClient
     * @param ftpPath     ftp路径
     * @param remoteFileName    ftp文件名
     * @param inputStream  本地输入流(本地要上传的文件)
     * @return
     */
    public static boolean uploadFile(FTPClient readyClient,
                                     String ftpPath,
                                     String remoteFileName, InputStream inputStream) {
        boolean success = false;
        try {
            if (!FTPReply.isPositiveCompletion(readyClient.getReplyCode())) {
                readyClient.disconnect();
                log.info("ftp上传失败!!!");
                log.info("ftp断开连接!!!");
                return success;
            }
            // 中文支持
            readyClient.setControlEncoding("UTF-8");
            //上传二进制文件
            readyClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            //ftp被动本地数据连接模式
            readyClient.enterLocalPassiveMode();
            //ftp工作路径
            readyClient.changeWorkingDirectory(ftpPath);
            //执行上传
            readyClient.storeFile(remoteFileName, inputStream);
            //关闭输入流
            inputStream.close();
            success = true;
            log.info("ftp上传成功!!!");
            readyClient.logout();
            log.info("ftp 退出登录!!!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (readyClient.isConnected()) {
                    readyClient.disconnect();
                    log.info("ftp断开连接!!!");
                }
            }catch (Exception e){
                log.info("ftp 断开连接失败");
                e.printStackTrace();
            }
        }
        return success;
    }


    /**
     * 下载ftp文件
     * @param readyClient  已登录的FTPClient
     * @param remoteFtpDir     ftp文件所在的目录
     * @param remoteFileName   ftp文件名(带后缀)
     * @param localOutPutStream
     */
    public static boolean downloadFtpFile(FTPClient readyClient , String remoteFtpDir,
                                       String remoteFileName,OutputStream localOutPutStream) {
        boolean success = false;

        try {
            if (!FTPReply.isPositiveCompletion(readyClient.getReplyCode())) {
                readyClient.disconnect();
                log.info("ftp下载失败!!!");
                log.info("ftp断开连接!!!");
                return success;
            }

            // 中文支持
            readyClient.setControlEncoding("UTF-8");
            //二进制
            readyClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            //ftp被动连接模式
            readyClient.enterLocalPassiveMode();
            //ftp工作路径
            readyClient.changeWorkingDirectory(remoteFtpDir);

            //下载到本地
            readyClient.retrieveFile(remoteFileName, localOutPutStream);

            //关闭输出流
            localOutPutStream.close();
            log.info("ftp文件下载成功!!!");
            success = true;

            //退出登录
            readyClient.logout();
            log.info("ftp 退出登录!!!");

        } catch (FileNotFoundException e) {
            System.out.println("没有找到" +  remoteFileName + "文件");
            e.printStackTrace();
        } catch (SocketException e) {
            System.out.println("连接FTP失败.");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("文件读取错误。");
            e.printStackTrace();
        }finally {
            try {
                if (readyClient.isConnected()) {
                    readyClient.disconnect();
                    log.info("ftp断开连接!!!");
                }
            }catch (Exception e){
                log.info("ftp 断开连接失败");
                e.printStackTrace();
            }
        }

        return success;


    }


    /**
     * ftp创建目录
     * @param newDir       新ftp目录
     * @param ftpClient    ftpClient
     * @return
     * @throws IOException
     */
    public static boolean createDirectory(String newDir ,FTPClient ftpClient) throws IOException {
        boolean isSuccess=false;
        try{
            isSuccess=ftpClient.makeDirectory(newDir);
        }catch(Exception e){
            e.printStackTrace();
        }
        return isSuccess;
    }




    /**
     * ftp判断目录是否存在(单层判断)
     * @param parentDir   父目录（一般为工作目录 /）
     * @param sonDirName      子目录
     * @param ftpClient   ftpClient
     * @return
     * @throws IOException
     */
    public static boolean existDirectory(String parentDir,String sonDirName ,FTPClient ftpClient) throws IOException {


        FTPFile[] ftpFiles = ftpClient.listDirectories(parentDir);
        for (FTPFile  file :ftpFiles){
            if (file.isDirectory() && StringUtils.equals(file.getName(),sonDirName)){

                log.info(parentDir+"下存在目录"+sonDirName);
                return true;
            }
        }
        return false;


    }


    /**
     * 判断某个目录下是否存在某个文件(多层判断,返回文件的全路径,如/aaatest/qdsd/qqq.txt)
     * 如果服务器目录较多，会很耗时
     * @param dir
     * @param fileName
     * @param ftpClient
     * @return
     * @throws Exception
     */
    public static String searchThisFile(String dir , String fileName , FTPClient ftpClient) throws Exception{


       boolean isExist =  ftpClient.changeWorkingDirectory(dir);
       if (! isExist){
           log.info("搜索目录不存在");
           return "";
       }

        FTPFile[] ftpFiles = ftpClient.listFiles(dir);
        for (FTPFile  file :ftpFiles){

//            System.out.println("-------------------");
//            System.out.println(file.getName());
//            System.out.println(file.getLink());
//            System.out.println(file.getUser());
//            System.out.println(file.getGroup());
//            System.out.println(file.getRawListing());

            if (file.isFile() && StringUtils.equals(fileName , file.getName())){
                if (StringUtils.endsWith(dir,"/")){
                    return dir+fileName;
                }else {
                    return   dir+"/"+fileName;
                }

            }

            if (file.isFile() && (!StringUtils.equals(fileName , file.getName()))){
                continue;
            }

            if (file.isDirectory()){

                if (file.getName().equals(".")||file.getName().equals("..")){
                    continue;
                }

                //递归进入内层目录,继续查找fileName
                String currentDir =  "";
                if (StringUtils.endsWith(dir,"/")){
                    currentDir =  dir+file.getName();
                }else {
                    currentDir =  dir+"/"+file.getName();
                }


                if (StringUtils.equals("",searchThisFile(currentDir,fileName , ftpClient))){
                    log.info("当前内层目录没找到,跳到下一个内层目录");
                    continue;
                }else {
                    return searchThisFile(currentDir,fileName , ftpClient);
                }

            }

        }


        return "";

    }









    /**
     * 删除ftp服务器单文件(单层)
     * @param fileDir   文件路径(不带文件名,如 "/aaatest")
     * @param fileName  文件名(带后缀,如 aaa.txt)
     * @param ftpClient  ftpClient
     * @return
     * @throws Exception
     */
    public static boolean deleteSingleFile(String fileDir,String fileName ,FTPClient ftpClient) throws Exception{

        boolean changeFlag = ftpClient.changeWorkingDirectory(fileDir);

        if (changeFlag){
            log.info("存在filDir:{}这个目录",fileDir);
            boolean deleteFlag = ftpClient.deleteFile(fileName);
            return deleteFlag;
        }else {
            log.info("不存在filDir:{}这个目录",fileDir);
            return false;
        }
    }


    /**
     * 删除空目录(单层)
     * @param emptyDir
     * @param ftpClient
     * @return
     * @throws Exception
     */
    public static boolean deleteFtpEmptyDir(String emptyDir , FTPClient ftpClient)throws Exception{

        boolean changeFlag = ftpClient.changeWorkingDirectory(emptyDir);

        if (changeFlag){
            log.info("存在 emptyDir:{}这个目录",emptyDir);
            return ftpClient.removeDirectory(emptyDir);

        }else {
            log.info("不存在 emptyDir:{}这个目录",emptyDir);
            return false;
        }

    }


    /**
     * 删除文件和目录(多层递归)
     * @param ftpClient
     * @param deleteFiles
     */
    public static void deleteServerFiles(FTPClient ftpClient, String deleteFiles) {
        /**如果 FTP 连接已经关闭，或者连接无效，则直接返回*/
        if (!ftpClient.isConnected() || !ftpClient.isAvailable()) {
            System.out.println(">>>>>FTP服务器连接已经关闭或者连接无效*****放弃删除文件****");
            return;
        }
        try {
            /** 尝试改变当前工作目录到 deleteFiles
             * 1）changeWorkingDirectory：变更FTPClient当前工作目录，变更成功返回true，否则失败返回false
             * 2）如果变更工作目录成功，则表示 deleteFiles 为服务器已经存在的目录
             * 3）否则变更失败，则认为 deleteFiles 是文件，是文件时则直接删除
             */
            boolean changeFlag = ftpClient.changeWorkingDirectory(deleteFiles);
            if (changeFlag) {
                /**当被删除的是目录时*/
                FTPFile[] ftpFiles = ftpClient.listFiles();
                for (FTPFile ftpFile : ftpFiles) {
                    System.out.println("--------------当前工作路径" + ftpClient.printWorkingDirectory());
                    if (ftpFile.isFile()) {
                        boolean deleteFlag = ftpClient.deleteFile(ftpFile.getName());
                        if (deleteFlag) {
                            System.out.println(">>>>>删除服务器文件成功****" + ftpFile.getName());
                        } else {
                            System.out.println(">>>>>删除服务器文件失败****" + ftpFile.getName());
                        }
                    } else {
                        /**printWorkingDirectory：获取 FTPClient 客户端当前工作目录
                         * 然后开始迭代删除子目录
                         */
                        String workingDirectory = ftpClient.printWorkingDirectory();
                        deleteServerFiles(ftpClient, workingDirectory + "/" + ftpFile.getName());
                    }
                }
                /**printWorkingDirectory：获取 FTPClient 客户端当前工作目录
                 * removeDirectory：删除FTP服务端的空目录，注意如果目录下存在子文件或者子目录，则删除失败
                 * 运行到这里表示目录下的内容已经删除完毕，此时再删除当前的为空的目录，同时将工作目录移动到上移层级
                 * */
                String workingDirectory = ftpClient.printWorkingDirectory();
                ftpClient.removeDirectory(workingDirectory);
                ftpClient.changeToParentDirectory();
            } else {
                /**deleteFile：删除FTP服务器上的文件
                 * 1）只用于删除文件而不是目录，删除成功时，返回 true
                 * 2）删除目录时无效,方法返回 false
                 * 3）待删除文件不存在时，删除失败，返回 false
                 * */
                boolean deleteFlag = ftpClient.deleteFile(deleteFiles);
                if (deleteFlag) {
                    System.out.println(">>>>>删除服务器文件成功****" + deleteFiles);
                } else {
                    System.out.println(">>>>>删除服务器文件失败****" + deleteFiles);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }








}
